package com.ayu.shortlink.admin.util;

import cn.hutool.core.util.StrUtil;
import com.ayu.shortlink.admin.common.client.CustomMinioClient;
import com.ayu.shortlink.admin.common.convention.exception.ServiceException;
import com.ayu.shortlink.admin.dto.req.FileUploadReqDTO;
import com.ayu.shortlink.admin.dto.resp.FileUploadRespDTO;
import com.google.common.collect.HashMultimap;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Part;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Component
public class MinioUtil{
    private CustomMinioClient customMinioClient;

    @Value("${minio.endpoint}")
    private String endpoint;
    @Value("${minio.accessKey}")
    private String accessKey;
    @Value("${minio.secretKey}")
    private String secretKey;
    @Value("${minio.bucketName}")
    private String bucketName;

    // spring自动注入会失败
    @PostConstruct
    public void init() {
        MinioAsyncClient minioClient = MinioAsyncClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
        customMinioClient = new CustomMinioClient(minioClient);
    }

    /**
     * 获取 Minio 中已经上传的分片文件
     * @param object 文件名称
     * @param uploadId 上传的文件id（由 minio 生成）
     * @return List<Integer>
     */
    @SneakyThrows
    public List<Integer> getListParts(String object, String uploadId) {
        List<Part> parts = getParts(object, uploadId);
        return parts.stream()
                .map(Part::partNumber)
                .collect(Collectors.toList());
    }

    /**
     * 单文件签名上传
     * @param object 文件名称（uuid 格式）
     * @return UploadUrlsVO
     */
    public FileUploadRespDTO singleUpload(String contentType, String object) {
        try {
            HashMultimap<String, String> headers = HashMultimap.create();
            contentType = contentType == null || StrUtil.isBlank(contentType) ?
                    "application/octet-stream":contentType;
            headers.put("Content-Type", contentType);

            // 生成UploadId
            String uploadId = UUID.randomUUID().toString();

            Map<String,String> queryParams = new HashMap<>();
            queryParams.put("uploadId", uploadId);

            String url = customMinioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.PUT)
                    .bucket(bucketName)
                    .object(object)
                    .extraHeaders(headers)
                    .extraQueryParams(queryParams)
                    // 先设置过期时间为10天
                    .expiry(10, TimeUnit.DAYS)
                    .build());

            return FileUploadRespDTO.builder()
                    .uploadID(uploadId)
                    .urls(List.of(url))
                    .build();
        } catch (Exception e) {
            throw new ServiceException("上传文件失败");
        }
    }

    /**
     * 初始化分片上传
     * @param requestParam 前端传入的文件信息
     * @param object object
     * @return UploadUrlsVO
     */
    public FileUploadRespDTO multiPartUpload(FileUploadReqDTO requestParam, String object){
        Integer chunkCount = requestParam.getChunkCount();
        String contentType = requestParam.getContentType();
        String uploadId = requestParam.getUploadId();

        try {
            HashMultimap<String, String> headers = HashMultimap.create();
            contentType = contentType == null || StrUtil.isBlank(contentType) ?
                    "application/octet-stream":contentType;
            headers.put("Content-Type", contentType);

            // 有UploadId则用 没有则生成UploadId
            uploadId = StrUtil.isBlank(uploadId) ?
                    customMinioClient.initMultiPartUpload(bucketName, null, object, headers, null):uploadId;

            Map<String,String> queryParams = new HashMap<>();

            queryParams.put("uploadId", uploadId);
            // 直接设置大小 就无需扩容
            List<String> partList = new ArrayList<>(chunkCount);
            for (int i = 1; i <= chunkCount; i++) {
                queryParams.put("partNumber", String.valueOf(i));

                String url = customMinioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                        .bucket(bucketName)
                        .expiry(10, TimeUnit.DAYS)
                        .object(object)
                        .extraHeaders(headers)
                        .extraQueryParams(queryParams)
                        .build());
                partList.add(url);
            }

            log.info("文件初始化成功");

            return FileUploadRespDTO.builder()
                    .uploadID(uploadId)
                    .urls(partList)
                    .build();
        }catch (Exception e){
            throw new ServiceException("上传失败");
        }
    }

    /**
     * 合并文件
     * @param object object
     * @param uploadId uploadUd
     */
    @SneakyThrows
    public boolean mergeMultipartUpload(String object, String uploadId) {
        log.info("通过 <{}-{}-{}> 合并<分片上传>数据", object, uploadId, bucketName);
        // 获取所有分片
        List<Part> partsList = getParts(object, uploadId);
        Part[] parts = new Part[partsList.size()];
        int partNumber = 1;
        for (Part part : partsList) {
            parts[partNumber - 1] = new Part(partNumber, part.etag());
            partNumber++;
        }
        // 合并分片
        customMinioClient.mergeMultipartUpload(bucketName, null, object, uploadId, parts, null, null);
        return true;
    }

    /**
     * 获取文件内容和元信息，该文件不存在会抛异常
     * @param object object
     * @return StatObjectResponse
     */
    @SneakyThrows
    public StatObjectResponse statObject(String object) {
        return customMinioClient.statObject(StatObjectArgs.builder()
                        .bucket(bucketName)
                        .object(object)
                        .build())
                .get();
    }

    @SneakyThrows
    public GetObjectResponse getObject(String object, Long offset, Long contentLength) {
        return customMinioClient.getObject(GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(object)
                        .offset(offset)
                        .length(contentLength)
                        .build())
                .get();
    }


    /**
     * 获取分片
     * @param object
     * @param uploadId
     * @return
     */
    @SneakyThrows
    public List<Part> getParts(String object, String uploadId){
        int partNumberMarker = 0;
        boolean isTruncated = true;
        List<Part> parts = new ArrayList<>();
        while(isTruncated){
            ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, object, 1000, partNumberMarker, uploadId, null, null);
            parts.addAll(partResult.result().partList());
            // 检查是否还有更多分片
            isTruncated = partResult.result().isTruncated();
            if (isTruncated) {
                // 更新partNumberMarker以获取下一页的分片数据
                partNumberMarker = partResult.result().nextPartNumberMarker();
            }
        }
        return parts;
    }

    /**
     * 查询Bucket是否存在
     * @param ExistsArgs
     * @return
     */
    @SneakyThrows
    public CompletableFuture<Boolean> bucketExists(BucketExistsArgs ExistsArgs){
        return customMinioClient.bucketExists(ExistsArgs);
    }

    /**
     * 创建Bucket
     * @param makeBucketArgs
     */
    @SneakyThrows
    public void makeBucket(MakeBucketArgs makeBucketArgs){
        customMinioClient.makeBucket(makeBucketArgs);
    }
}
